/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
 * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see
 * <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.binding.beanSpec;

import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import org.jdesktop.observablecollections.*;

/**
 * List tracking the state of it's content and itself.
 * 
 * @author M. Hautle
 * @param <T> The bean type
 */
public class StatusList<T> extends AbstractList<T> implements ObservableList<T>, ChildStatus, Serializable {
	// CHECKME Initial Values speichern? -> Im moment erkennt die Liste nicht wenn der "Urzustand" wieder hergestellt
	// wurde
	/** The wrapped list */
	private transient final List<T> list;

	/** Listeners */
	private transient final CopyOnWriteArrayList<ObservableListListener> listeners;

	/** Owners of this list */
	private transient final ArrayList<ChildStatus> owners = new ArrayList<ChildStatus>(1);

	/** The state of the list */
	private transient BeanStatusFlag status = BeanStatusFlag.UNCHANGED;

	/** The state of the structure */
	private transient BeanStatusFlag structureStatus = BeanStatusFlag.UNCHANGED;

	/** The number of childs containting properties with the {@link BeanStatusFlag#ERROR} flag */
	private transient int structureErrorCount = 0;

	/** The number of childs containting properties with the {@link BeanStatusFlag#CHANGED} flag */
	private transient int structureChangedCount = 0;

	/**
	 * Creates a {@link StatusList} and registers the {@link BeanStatusImpl} of the owning bean as listener<br>
	 * 
	 * @param <T> The contenttype of the list
	 * @param list The list to wrap
	 * @param owner The {@link BeanStatusImpl}/{@link StatusList} of the owning bean
	 * @return A new {@link StatusList} wrapping the given or the given list if it's already a {@link StatusList}
	 */
	static <T> StatusList<T> create(final List<T> list, final ChildStatus owner) {
		StatusList<T> l;
		if (list instanceof StatusList)
			l = (StatusList<T>) list;
		else
			l = new StatusList<T>(list);
		owner.childStatusChanged(BeanStatusFlag.UNCHANGED, l.status);
		l.owners.add(owner);
		return l;
	}

	/**
	 * Removes a listener previously added using {@link #create(List, ChildStatus)} and informs the owner that this list
	 * has been deleted.<br>
	 * 
	 * @param list The concerned list
	 * @param owner The {@link BeanStatusImpl} of the owning bean
	 */
	static void removed(final List list, final ChildStatus owner) {
		if (!(list instanceof StatusList))
			return;
		final StatusList l = (StatusList) list;
		if (l.owners.remove(owner))
			owner.childStatusChanged(l.status, BeanStatusFlag.UNCHANGED);
	}

	/**
	 * Default constructor
	 * 
	 * @param list The list to wrap
	 */
	public StatusList(final List<T> list) {
		this.list = list;
		listeners = new CopyOnWriteArrayList<ObservableListListener>();
		// register a status listener on each element
		for (final T e : list)
			if (e instanceof StatusInformationInternal)
				((StatusInformationInternal) e)._getBeanStatus().registerOwner(this);
	}

	/**
	 * Returns the list status.<br>
	 * The {@link BeanStatusFlag#CHANGED} means that at least one element was added/removed.
	 * 
	 * @return The list status
	 */
	public BeanStatusFlag getStatus() {
		return status;
	}

	/**
	 * Returns the structure status.
	 * 
	 * @return The structure status
	 */
	public BeanStatusFlag getStructureStatus() {
		return structureStatus;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public T get(final int index) {
		return list.get(index);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int size() {
		return list.size();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public T set(final int index, final T element) {
		final T oldValue = list.set(index, element);
		objectRemoved(oldValue);
		objectAdded(element);
		for (final ObservableListListener listener : listeners)
			listener.listElementReplaced(this, index, oldValue);
		return oldValue;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void add(final int index, final T element) {
		list.add(index, element);
		objectAdded(element);
		modCount++;
		for (final ObservableListListener listener : listeners)
			listener.listElementsAdded(this, index, 1);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public T remove(final int index) {
		final T oldValue = list.remove(index);
		modCount++;
		objectRemoved(oldValue);
		for (final ObservableListListener listener : listeners)
			listener.listElementsRemoved(this, index, Collections.singletonList(oldValue));
		return oldValue;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean addAll(final Collection<? extends T> c) {
		return addAll(size(), c);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean addAll(final int index, final Collection<? extends T> c) {
		if (!list.addAll(index, c))
			return false;
		for (final T e : c)
			objectAdded(e);
		modCount++;
		for (final ObservableListListener listener : listeners)
			listener.listElementsAdded(this, index, c.size());
		return true;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void clear() {
		if (list.size() == 0)
			return;
		final List<T> dup = new ArrayList<T>(list);
		for (final T e : dup)
			objectRemoved(e);
		list.clear();
		modCount++;
		for (final ObservableListListener listener : listeners) {
			listener.listElementsRemoved(this, 0, dup);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean containsAll(final Collection<?> c) {
		return list.containsAll(c);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <U extends Object> U[] toArray(U[] a) {
		return list.toArray(a);
	};

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Object[] toArray() {
		return list.toArray();
	}

	/**
	 * {@inheritDoc}
	 */
	public void addObservableListListener(final ObservableListListener listener) {
		listeners.add(listener);
	}

	/**
	 * {@inheritDoc}
	 */
	public void removeObservableListListener(final ObservableListListener listener) {
		listeners.remove(listener);
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean supportsElementPropertyChanged() {
		return false;
	}

	/**
	 * Registers the list as owner if it's a "TrackedBean" and sets the list state to {@link BeanStatusFlag#CHANGED}
	 * 
	 * @param o The added object
	 */
	private void objectAdded(final Object o) {
		if (status == BeanStatusFlag.UNCHANGED) {
			status = BeanStatusFlag.CHANGED;
			childStatusChanged(BeanStatusFlag.UNCHANGED, BeanStatusFlag.CHANGED);
		}
		if (o instanceof StatusInformationInternal)
			((StatusInformationInternal) o)._getBeanStatus().registerOwner(this);
	}

	/**
	 * Unregisters the list as owner if it's a "TrackedBean" and sets the list state to {@link BeanStatusFlag#CHANGED}
	 * 
	 * @param o The removed object
	 */
	private void objectRemoved(final Object o) {
		if (status == BeanStatusFlag.UNCHANGED) {
			status = BeanStatusFlag.CHANGED;
			childStatusChanged(BeanStatusFlag.UNCHANGED, BeanStatusFlag.CHANGED);
		}
		if (o instanceof StatusInformationInternal)
			((StatusInformationInternal) o)._getBeanStatus().unregisterOwner(this);
	}

	/**
	 * <b>Only for internal use!</b><br>
	 * This method is public as implementation side effect!
	 * 
	 * @param oldStatus The old status of the child
	 * @param newStatus The new status of the child
	 */
	public void childStatusChanged(final BeanStatusFlag oldStatus, final BeanStatusFlag newStatus) {
		structureErrorCount += newStatus.errorValue - oldStatus.errorValue;
		structureChangedCount += newStatus.changedValue - oldStatus.changedValue;
		// update the beansStructure status
		if (structureErrorCount == 0) {
			final BeanStatusFlag newFlag = structureChangedCount == 0 ? BeanStatusFlag.UNCHANGED : BeanStatusFlag.CHANGED;
			if (newFlag != structureStatus)
				setStructureStatus(newFlag);
		} else if (BeanStatusFlag.ERROR != structureStatus) {
			setStructureStatus(BeanStatusFlag.ERROR);
		}
	}

	/**
	 * Sets the new strucure status and informs the owners about the change
	 * 
	 * @param newStatus The new structure status
	 */
	private void setStructureStatus(final BeanStatusFlag newStatus) {
		final BeanStatusFlag old = structureStatus;
		structureStatus = newStatus;
		for (final ChildStatus o : owners)
			o.childStatusChanged(old, newStatus);
	}

	/**
	 * In case of serialization write just the wrapped list out
	 * 
	 * @return The wrapped list
	 */
	private Object writeReplace() {
		return list;
	}
}
